package sicnu.cs.ich.security.aop;

import com.alibaba.fastjson.JSON;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import sicnu.cs.ich.api.common.ResultInfo;
import sicnu.cs.ich.api.common.annotations.OperLog;
import sicnu.cs.ich.api.common.enums.OperEnum;
import sicnu.cs.ich.api.common.enums.ResultEnum;
import sicnu.cs.ich.api.domain.entity.OperationLog;
import sicnu.cs.ich.common.util.RequestUtil;
import sicnu.cs.ich.security.util.SecurityUtil;

import java.util.Date;
import java.util.Objects;
import java.util.Optional;

/**
 * 记录操作日志的切面
 *
 * @author CaiKe
 * @date 2021/11/13 18:08
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class OperLogAOP {
    private final DefaultMQProducer producer;

    /**
     * 正常操作日志切入点
     */
    @Pointcut("@annotation(sicnu.cs.ich.api.common.annotations.OperLog)")
    public void LogPointCut() {
    }

    /**
     * 方法正常返回时拦截用户操作日志（如果连接点抛出异常，则不执行）
     *
     * @param joinPoint
     * @param rs
     */
    @SuppressWarnings("rawtypes")
    @AfterReturning(value = "LogPointCut()", returning = "rs")
    public void SaveOperationLog(JoinPoint joinPoint, Object rs) {
        if (!((ResultInfo) rs).getCode().equals(ResultEnum.OK.getCode())) {
            // 若非buildSuccess，则不记录日志
            return;
        }
        OperationLog operLog = new OperationLog();
        // 从切入点获取指定的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 设置方法信息
        val method = signature.getMethod();
        val clazz = joinPoint.getTarget().getClass();
        final String className = clazz.getName();
        operLog.setMethod(className + "::" + method.getName());
        // 从注解获取操作信息
        final OperLog operAnno = method.getAnnotation(OperLog.class);
        if (operAnno != null) {
            // 如果Module为空，则使用类上的Api
            if (operAnno.module().isEmpty()) {
                Optional.ofNullable(clazz.getAnnotation(Api.class))
                        .ifPresentOrElse(
                                v -> operLog.setModule(v.tags()[0]),
                                () -> operLog.setModule("")
                        );
            } else {
                operLog.setModule(operAnno.module());
            }
            operLog.setType(operAnno.type());
            // 如果description为“”,则使用ApiOperation代替
            if (operAnno.description().isEmpty()) {
                Optional.ofNullable(method.getAnnotation(ApiOperation.class))
                        .ifPresentOrElse(
                                v -> operLog.setDescription(v.value()),
                                () -> operLog.setDescription("")
                        );
            } else {
                operLog.setDescription(operAnno.description());
            }
            // 设置出参信息(rawTypes)
            // 查询行为不保存返回信息
            if (Objects.equals(operAnno.type(), OperEnum.QUERY))
                operLog.setRespParam("");
            else
                operLog.setRespParam(JSON.toJSONString(((ResultInfo) rs).getData()));
        }
        // 设置请求参数信息
        operLog.setReqParam(JSON.toJSONString(joinPoint.getArgs()));
        operLog.setUserId(SecurityUtil.getCurrentUserId());
        operLog.setUri(RequestUtil.getCurrentUrl());
        operLog.setIp(RequestUtil.getIpAddress());
        operLog.setCreateTime(new Date());

        log.info(operLog.toString());
        // TODO 这里阻塞也会导致请求阻塞（后续采用消息队列优化）
//        logMapper.insert(operLog);

        try {
            producer.send(new Message("log", "oper", JSON.toJSONBytes(operLog)));
        } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) {
            log.error("日志发送失败：{}", operLog);
            e.printStackTrace();
        }
    }


}